{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ParameterNode guide" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `ParameterNode` is a container that can group parameters, and also other `ParameterNodes`. \n", "For those familiar with object-oriented programming, the `ParameterNode` can be viewed as the object, and the `Parameters` as the attributes. \n", "\n", "One distinction with is that ParameterNodes can be nested, and so another way to look at a ParameterNode is as a dictionary, where its items can either be other dictionaries (ParameterNodes), or values (Parameters). It is therefore recommended to have a basic understanding of Python dictionaries, see e.g. https://www.w3schools.com/python/python_dictionaries.asp\n", "\n", "One example of a `ParameterNode` is an `Instrument`, which corresponds to a physical instrument. \n", "However, the `ParameterNode` does not need to be limited to actual instruments." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ParameterNode with Parameters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we create a ParameterNode and add a Parameter to it. \n", "Note that we do not need to specify the parameter name, it is automatically deduced from the attribute name:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "node.p.name = p\n" ] } ], "source": [ "from qcodes import ParameterNode, Parameter\n", "\n", "node = ParameterNode('node')\n", "node.p = Parameter(set_cmd=None, initial_value=1) # Can also use node.add_parameter('p', set_cmd=None)\n", "print('node.p.name =', node.p.name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `Parameter` is registered in the ParameterNode's attribute `parameters`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'p': }" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "node.parameters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similarly, `ParameterNode` is added as the parent of the `Parameter`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "ParameterNode node containing 1 parameters" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "node.p.parent" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is also reflected when we get a string representation of the parameter:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'node_p'" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "str(node.p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Note**: Once a `Parameter` is added to a `ParameterNode`, adding the `Parameter` to other `ParameterNodes` does not change it's parent" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ParameterNode containing ParameterNodes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A ParameterNode can also contain other ParameterNodes:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'node_subnode_p'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "node.subnode = ParameterNode()\n", "\n", "node.subnode.p = Parameter(set_cmd=None)\n", "\n", "str(node.subnode.p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simplified snapshotting" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When performing a measurement, a snapshot is created of all Parameters and ParameterNodes in the station. \n", "These snapshots can become quite messy:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'__class__': 'qcodes.instrument.parameter_node.ParameterNode',\n", " 'functions': {},\n", " 'name': 'node',\n", " 'parameter_nodes': {'subnode': {'__class__': 'qcodes.instrument.parameter_node.ParameterNode',\n", " 'functions': {},\n", " 'name': 'subnode',\n", " 'parameter_nodes': {},\n", " 'parameters': {'p': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'node_subnode_p',\n", " 'label': 'P',\n", " 'name': 'p',\n", " 'raw_value': None,\n", " 'ts': None,\n", " 'value': None}},\n", " 'submodules': {}}},\n", " 'parameters': {'p': {'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'node_p',\n", " 'label': 'P',\n", " 'name': 'p',\n", " 'raw_value': 1,\n", " 'ts': '2018-11-22 17:23:15',\n", " 'value': 1}},\n", " 'submodules': {}}" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "node.snapshot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Depending on the type of ParameterNode, we may only be interested in the values of the parameters and nodes. \n", "For this reason, we can improve the readability of the snapshot by turning on `simplify_snapshot`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "node.simplify_snapshot = True\n", "node.subnode.simplify_snapshot = True" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'__class__': 'qcodes.instrument.parameter_node.ParameterNode',\n", " 'p': 1,\n", " 'subnode': {'__class__': 'qcodes.instrument.parameter_node.ParameterNode',\n", " 'p': None}}" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "node.snapshot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also print a snapshot of all parameters and parameter nodes:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "node :\n", "\tparameter value\n", "--------------------------------------------------------------------------------\n", "p :\t1 \n", "\n", "node_subnode :\n", "\tparameter value\n", "--------------------------------------------------------------------------------\n", "p :\tNone \n" ] } ], "source": [ "node.print_snapshot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Accessing Parameters as attributes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One may have noticed that getting/setting a parameter is different from standard python classes. \n", "For parameters, this is done by function calls:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "42" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "node = ParameterNode()\n", "node.p = Parameter(set_cmd=None)\n", "node.p(42) # Setting value\n", "node.p() # Getting value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Compare this to a standard python class, where the attribute behaves just like the variable it represents" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class C:\n", " pass\n", " \n", "c = C()\n", "c.p = 1 # Setting value\n", "c.p # Getting value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This can be confusing, especially if we want to convert abstract classes into `ParameterNodes` and `Parameters`. \n", "We would need to remember which classes are and are not ParameterNodes, and which attributes are and are not Parameters. \n", "As a solution to this problem, we can pass the keyword argument `use_as_atttributes` to a ParameterNode:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "42" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "node = ParameterNode(use_as_attributes=True)\n", "node.p = Parameter(set_cmd=None)\n", "node.p = 42 # Setting value\n", "node.p # Getting value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that now the Parameter attributes behave just like the variables they represent. \n", "There are cases, however, where you still want to access the Parameter object instead of its value. \n", "In this case, you can access the Parameter like you would in a dictionary:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] }, { "data": { "text/plain": [ "{'__class__': 'qcodes.instrument.parameter.Parameter',\n", " 'full_name': 'p',\n", " 'label': 'P',\n", " 'name': 'p',\n", " 'raw_value': 42,\n", " 'ts': '2018-11-22 17:23:18',\n", " 'value': 42}" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(repr(node['p']))\n", "node['p'].snapshot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Warning**: The option `use_as_attributes` can have unintended consequences, as every call to the attribute will trigger it's `get` function. \n", "This could cause unnecessary get calls if this is not taken into account:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Expensive get command called\n", "None\n", "Expensive get command called\n", "None\n", "Expensive get command called\n", "None\n", "Expensive get command called\n", "None\n", "Expensive get command called\n", "None\n" ] } ], "source": [ "node = ParameterNode(use_as_attributes=True)\n", "node.p = Parameter(get_cmd=lambda: print('Expensive get command called'))\n", "\n", "for k in range(5):\n", " print(node.p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a general rule, all Instruments should have `use_as_attributes=False` to ensure that we don't accidentally perform actions on instruments that may negatively impact the experiment. \n", "For more abstract ParameterNodes, especially ones where parameters don't perform ancillary get/set functions, using `use_as_attributes=True` is preferred." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Defining Parameter properties via the ParameterNode" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In more advanced cases, a ParameterNode contains parameters whose get/set need \n", "While in principle these functions can be passed" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In more advanced cases, the Parameter may not be isolated, but instead depends on other Parameters in its ParameterNode. \n", "In these cases, every time" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As an example, lets say we have a `Pulse` class with a start time `t_start` and stop time `t_stop`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Pulse(t_start=1, t_stop=3)" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Pulse(ParameterNode):\n", " def __init__(self, t_start, t_stop, **kwargs):\n", " super().__init__(use_as_attributes=True, **kwargs)\n", " self.t_start = Parameter(set_cmd=None, initial_value=t_start)\n", " self.t_stop = Parameter(set_cmd=None, initial_value=t_stop)\n", " \n", " def __repr__(self):\n", " return f'Pulse(t_start={self.t_start}, t_stop={self.t_stop})'\n", " \n", "Pulse(1, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So far so good. Now we also want this Pulse to have a duration, defined by `duration = t_stop - t_start`. \n", "When we set the `duration`, we want `t_start` to remain fixed, and `t_stop` should be changed accordingly. \n", "This adds a complication, as the parameter `duration` depends on two other parameters. \n", "\n", "A basic solution would be also instantiate the `duration` parameter with get_cmd and set_cmd, as such:\n", "```Python\n", "self.duration = Parameter(get_cmd=lambda: self.t_stop - self.t_start, \n", " set_cmd=lambda duration: setattr(self, 't_stop', self.t_start + duration))\n", "```\n", "While this in principle works, it fails for functions that are not one-liners, plus using lambda functions can have unintended consequences.\n", "Other solutions are to either define the get/set functions elsewhere, or to subclass the Parameter and explicitly defining a `get_raw` and `set_raw` method. Both these options are not ideal, one of the reasons being that it obfuscates code. \n", "\n", "As an alternative, we can define the get/set of the Parameter as methods in the ParameterNode:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Pulse(t_start=1, t_stop=3, duration=2)" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from qcodes.instrument.parameter_node import parameter\n", "class Pulse(ParameterNode):\n", " def __init__(self, t_start, t_stop, **kwargs):\n", " super().__init__(use_as_attributes=True, **kwargs)\n", " self.t_start = Parameter(set_cmd=None, initial_value=t_start)\n", " self.t_stop = Parameter(set_cmd=None, initial_value=t_stop)\n", " self.duration = Parameter()\n", " \n", " @parameter\n", " def duration_get(self, parameter):\n", " return self.t_stop - self.t_start\n", " \n", " @parameter\n", " def duration_set(self, parameter, duration):\n", " self.t_stop = self.t_start + duration\n", " \n", " def __repr__(self):\n", " return f'Pulse(t_start={self.t_start}, t_stop={self.t_stop}, duration={self.duration})'\n", " \n", "pulse = Pulse(1, 3)\n", "pulse" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Pulse(t_start=1, t_stop=6, duration=5)" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pulse.duration = 5\n", "pulse" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that in this case the duration parameter can easily access other parameters in the parameter node. \n", "We use the decorator `@parameter` to define that the method belongs to a Parameter, and the method should have the form `{parameter_name}_{function}`, where `{function}` can be get/set/get_parser/set_parser/vals" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.3" } }, "nbformat": 4, "nbformat_minor": 2 }